JavaScript 中的对象属性,并不只是简单的键值对,通过属性描述符 property descriptor ,我们可以更加灵活的配置对象属性以控制属性的行为,包括是否可枚举、是否可写、是否可配置,实现更强大的功能。实际上,对于对象而言,一共有 2 种属性:
- 数据属性 data property
- 访问属性 accessor property
数据属性
通常我们见到的属性都是这样的:
1 | // 代码片段 1 |
上面的 name
和 age
都是数据属性。
访问属性
还有另一种形式,比如:
1 | // 代码片段 2 |
上面的 name
和 surname
是数据属性,fullname
则是访问属性。即凡是使用 get prop()
或者 set prop()
定义的属性,都是访问属性,不再是数据属性。
数据属性的描述符
对于数据属性而言,其描述符由 4 个 flag
组成:
value
属性的值writable
是否可写enumerable
是否可枚举,在类似for...in
遍历中是否忽略configurable
是否可配置,即是否可以编辑描述符的flag
对于使用字面量声明的对象属性而言,除了 value
之外的 3 个 flag
的默认值都为 true
。使用 Object.getOwnPropertyDescriptor(obj, prop)
方法可以获取对象上某属性的描述符。比如获取上面代码片段 1 中 user
对象 name
属性的描述符:
1 | // 代码片段 3 |
如果想要精确地控制属性的行为,使用 Object.defineProperty(obj, prop, descriptor)
方法。使用这种方式,如果属性的某个 flag
没有显式指定,则默认值将是 false
。
1 | // 代码片段 4 |
可以看到,除了显式指定的 writable
,其他未指定的 flag
的都是默认为 false
。
可枚举性
描述符的 enumerable
属性,称为“可枚举性”,如果该属性为 false
,就表示某些操作会忽略当前属性。
目前,有四个操作会忽略 enumerable
为 false
的属性。
for...in
循环:只遍历对象自身的和继承的可枚举的属性。Object.keys()
:返回对象自身的所有可枚举的属性的键名。JSON.stringify()
:只串行化对象自身的可枚举的属性。Object.assign()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。
可配置性
描述符的 configurable
属性,表示该属性的 flag
是否可配置,一旦设为 false
不可逆。用的很少。
访问属性的描述符
访问属性的描述符不同于数据属性的描述符,没有 value
和 writable
这 2 个 flag
,多了 get
和 set
,所以一个访问属性的描述符可能有如下组成:
get
– 无参数函数,当属性被读取时被调用set
– 单参数函数,当属性被设置时被调用enumerable
– 与数据属性一致configurable
– 与数据属性一致
访问属性的 getter/setter
可以直接使用字面量声明(如上面的代码片段 2 所示),也可以使用 Object.defineProperty
,如下:
1 | // 代码片段 5 |
getter/setter 应用
使用 get
和 set
访问属性描述符,我们可以更加灵活控制属性的读写行为。比如对于 user
对象而言,可以限制 name
的长度:
1 | // 代码片段 6 |
数据属性或访问属性
需要注意的是,一个属性要么是数据属性,要么是访问属性,不能两者皆是。相应地,如果同时给属性设置 value
和 get
这 2 个 flag
会报错。
1 | // 代码片段 7 |